package tp.rest;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.util.JAXBSource;
import javax.xml.transform.Source;
import javax.xml.ws.Endpoint;
import javax.xml.ws.Provider;
import javax.xml.ws.Service;
import javax.xml.ws.ServiceMode;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.WebServiceProvider;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.http.HTTPBinding;

import tp.model.City;
import tp.model.CityManager;
import tp.model.CityNotFound;
import tp.model.NoCityFound;
import tp.model.Position;

/**
 * Serveur de l'application.
 * @author Anton KISSELEFF & Nicolas LETHUILLIER
 */
@WebServiceProvider
@ServiceMode(value=Service.Mode.MESSAGE)
public class MyServiceTP implements Provider<Source> {
	
	// ATTRIBUTS
	
	@javax.annotation.Resource(type=Object.class)
	protected WebServiceContext wsContext;
	
	private CityManager cityManager = new CityManager();
	
	private JAXBContext jc;
	
	// CONSTRUCTEURS
	
	public MyServiceTP() {
		try {
            jc = JAXBContext.newInstance(CityManager.class,City.class,Position.class,NoCityFound.class);
            
        } catch(JAXBException je) {
            System.out.println("Exception " + je);
            throw new WebServiceException("Cannot create JAXBContext", je);
        }
        cityManager.addCity(new City("Rouen",49.437994,1.132965,"FR"));
        cityManager.addCity(new City("Neuor",12,42,"RF"));
	}
	
	// REQUETES
	
    public Source invoke(Source source) {
    	
        try{
            MessageContext mc = wsContext.getMessageContext();
            String path = (String)mc.get(MessageContext.PATH_INFO);
            String method = (String)mc.get(MessageContext.HTTP_REQUEST_METHOD);
            System.out.println("Got HTTP "+method+" request for "+path);
		    if (method.equals("GET"))
	                return get(mc);
			if (method.equals("POST"))
				    return post(source, mc);
           	if (method.equals("PUT"))
					return put(source, mc);
           	if (method.equals("DELETE"))
					return delete(source, mc);
			throw new WebServiceException("Unsupported method:" +method);  
        } catch(JAXBException je) {
            throw new WebServiceException(je);
        }
    }
    
    // OUTILS

    /**
     * Ajoute une {@link City} à la liste des villes enregistrées.
     * @param source la ville transmise à enregistrer
     * @param mc le message envoyé
     * @return le {@link CityManager} actualisé
     * @throws JAXBException
     */
	private Source put(Source source, MessageContext mc) throws JAXBException {
		Unmarshaller u = jc.createUnmarshaller();
		City city = (City) u.unmarshal(source);
		cityManager.addCity(city);
		return new JAXBSource(jc, cityManager);
	}

	/**
	 * Supprime la {@link City} transmise dans le message ou toutes les {@link City}s si le message est "all".
	 * @param source un objet quelconque, inutilisé
	 * @param mc le message envoyé, contenant "all" ou une string décrivant une {@link City}
	 * @return le {@link CityManager} actualisé
	 * @throws JAXBException
	 */
	private Source delete(Source source, MessageContext mc) throws JAXBException {
		String param = (String) mc.get(MessageContext.QUERY_STRING);
		if (param != null) {
			List<City> cities = cityManager.getCities();
			if (param.equals("all")) {
				cityManager = new CityManager();
			} else {
				for (City c : cities) {
					if (c.toString().replace(" ", "_").equals(param)) {
						cityManager.removeCity(c);
						break;
					}
				}
			}
		}
		
		return new JAXBSource(jc, cityManager);
	}

	/**
	 * Retourne la {@link City} présente à la {@link Position} envoyée.
	 * Si le message est "near", recherche toutes les {@link City}s proches de cette {@link Position}.
	 * @param source la {@link Position} où chercher
	 * @param mc le message envoyé
	 * @return la ou les {@link City}s concernées.
	 * @throws JAXBException
	 */
	private Source post(Source source, MessageContext mc) throws JAXBException {
		Unmarshaller u = jc.createUnmarshaller();
		Position position = (Position) u.unmarshal(source);
		String param = (String) mc.get(MessageContext.QUERY_STRING);
		Object message;
		if (param != null && param.equals("near")) {
			CityManager mgr = cityManager.searchNear(position);
			message = mgr.getCities().size() == 0 ? new NoCityFound(position) : mgr;
		} else {
			try {
				message = cityManager.searchExactPosition(position);
			} catch (CityNotFound cnf) {
				message = new NoCityFound(position);
			}
		}
		
		return new JAXBSource(jc, message);

	}

	/**
	 * Retourne toutes les {@link City}s dont le nom correspond au message envoyé.
	 * Celui-ci peut être séparé en plusieurs mots-clés par le caractère &.
	 * Si le message est "all", la méthode affiche toutes les {@link City}s.
	 * Si le message est vide, aucune {@link City} n'est retournée.
	 * @param mc le message envoyé
	 * @return les {@link City}s correspondantes
	 * @throws JAXBException
	 */
	private Source get(MessageContext mc) throws JAXBException {
		String str = (String) mc.get(MessageContext.QUERY_STRING);
		if (str != null) {
			if (str.equalsIgnoreCase("all")) {
				return new JAXBSource(jc, cityManager);
			}
			String[] keys = str.split("&");
			CityManager mgr = new CityManager();
			for (String s : keys) {
				List<City> cities = cityManager.searchFor(s);
				for (City c : cities) {
					mgr.addCity(c);
				}
			}
			return new JAXBSource(jc, mgr.getCities().size() == 0 ? new NoCityFound() : mgr);
		}
		return new JAXBSource(jc, new CityManager());
	}
	
	// MAIN

	public static void main(String args[]) {
	      Endpoint e = Endpoint.create( HTTPBinding.HTTP_BINDING, new MyServiceTP());
	      e.publish("http://127.0.0.1:8080/");
	       // pour arrêter : e.stop();
	 }
}
